package com.zed.games.scene;

import cn.hutool.core.util.IdUtil;
import com.zed.api.common.ApplicationContextAwareExtend;
import com.zed.api.common.ApplicationContextSupport;
import com.zed.api.common.Registration;
import com.zed.api.games.*;
import com.zed.api.games.event.GameEndingEvent;
import com.zed.api.games.event.GameStartingEvent;
import com.zed.api.games.scene.GameLifeCycle;
import com.zed.games.GameRegister;
import reactor.core.publisher.Mono;

import java.util.Objects;

/**
 * 缺省的游戏生命周期
 *
 * @author Zed
 */
public class DefaultGameLifeCycle extends ApplicationContextSupport implements GameLifeCycle {

    private final GameQueryRepository gameQueryRepository;

    private GameRegister gameRegister;

    private final GameBuilderRepository gameBuilderRepository;

    /**
     * 构造函数
     *
     * @param gameQueryRepository   {@link GameQueryRepository},运行时游戏查询仓储
     * @param gameBuilderRepository {@link GameBuilderRepository},游戏待构建仓储
     */
    public DefaultGameLifeCycle(GameQueryRepository gameQueryRepository, GameBuilderRepository gameBuilderRepository) {
        this.gameQueryRepository = Objects.requireNonNull(gameQueryRepository, "\"gameQueryRepository\" cannot be null.");
        this.gameBuilderRepository = Objects.requireNonNull(gameBuilderRepository, "\"gameBuilderRepository\" cannot be null.");
        if (this.gameQueryRepository instanceof GameRegister) {
            this.gameRegister = (GameRegister) this.gameQueryRepository;
        }
    }

    /**
     * 确保游戏构建器存在
     *
     * @return {@link GameBuilder},游戏构建器
     */
    protected GameBuilder<?, ? extends Action> ensureGameBuilderExist(String gameBuilderId) throws GameBuilderNotExistException {
        GameBuilder<?, ? extends Action> gameBuilder = this.getGameBuilderRepository().findById(gameBuilderId);
        if (gameBuilder == null) {
            throw new GameBuilderNotExistException(gameBuilderId.concat(" not exist"));
        }
        return gameBuilder;
    }

    @Override
    public Mono<String> startGame(String gameBuilderId) {
        try {
            GameBuilder<?, ? extends Action> gameBuilder = this.ensureGameBuilderExist(gameBuilderId);

            Game runningGame = this.getGameQueryRepository().getAccountRunningGame(gameBuilderId);

            if (runningGame != null) {
                return Mono.just(((ImmutableGameWrapper) runningGame).getId());
            }

            Game game = gameBuilder.build(this.getContext());
            if (game instanceof ImmutableGameWrapper) {
                throw new GameException(ImmutableGameWrapper.class.getName().concat(" with system reserve class,please don't return"));
            }

            ApplicationContextAwareExtend.tryToBind(game, this.getContext());
            ImmutableGameWrapper gameWrapper = new ImmutableGameWrapper(IdUtil.getSnowflakeNextIdStr(), game);
            this.getGameRegister().register(gameWrapper);
            this.publishEvent(new GameStartingEvent(gameWrapper));
            return Mono.just(gameWrapper.getId());
        } catch (Exception e) {
            return Mono.error(e);
        }
    }

    /**
     * 确保游戏存在并返回
     *
     * @param gameId {@link String},游戏主键
     * @return {@link Game},运行时游戏
     * @throws RunningGameNotExistException, 运行时游戏不存在在
     */
    protected Game ensureRunningGameExist(String gameId) throws RunningGameNotExistException {
        Game runningGame = this.getGameQueryRepository().findById(gameId);
        if (runningGame == null) {
            throw new RunningGameNotExistException(gameId);
        }
        return runningGame;
    }

    @Override
    public Mono<String> endGame(String gameId) {
        try {
            ImmutableGameWrapper immutableGame = (ImmutableGameWrapper) this.ensureRunningGameExist(gameId);

            Game moduleRunningGame = immutableGame.getOriginal();

            if (moduleRunningGame instanceof Registration) {
                ((Registration) moduleRunningGame).unregister();
            }

            this.getGameRegister().unRegistet(immutableGame);

            /**
             * 广播游戏结束,交给某些服务做资源结算
             */
            this.publishEvent(new GameEndingEvent(immutableGame));
            return Mono.just(gameId);
        } catch (Exception e) {
            return Mono.error(e);
        }
    }

    /**
     * 获取运行时游戏查询仓储
     *
     * @return {@link GameQueryRepository}
     */
    public GameQueryRepository getGameQueryRepository() {
        return this.gameQueryRepository;
    }

    /**
     * 获取游戏注册器
     *
     * @return {@link GameRegister}
     */
    public GameRegister getGameRegister() {
        return this.gameRegister;
    }

    /**
     * 获取游戏构建仓储
     *
     * @return {@link GameBuilderRepository}
     */
    public GameBuilderRepository getGameBuilderRepository() {
        return this.gameBuilderRepository;
    }
}
